﻿using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using HK.Core.IO.Data;
using HK.Core.IO.Protocol.Data;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using HLogger = HK.Core.Logs.Logger;

namespace HK.Core.Utils
{
    /*
     * JAB（Joint Asset Bundle）是将多个ab包拼接成单个文件并附带压缩、文件日期体积基础信息等内容的格式
     * 此格式能缓解用户下载过于零散的ab包以至于在创建网络连接上投入过多时间
     * 也能用来辅助压缩XML等原本不压缩的格式
     *
     *jab格式定义{
     * * HEAD_SECTION:
     * byte version
     * byte compress	//版本2新增，1表示zlib压缩
     * uint dataSectionGlobalOffset = DATA_SECTION		//数据段位置
     * uint numFiles
     * UTF prefix		//版本3新增，写入文件前缀
     * MENU_SECTION:
     * struct jab[numFiles]{
     *		byte rPath		//路径相对于上一个jab文件有多少字符重复
     *		byte type		//后缀名的类型，枚举，见后文解析部分
     *		UTF path		//除上两字段描述外的路径
     *		int dataLocalOffset		//有效数据在数据段中的偏移
     *		int size
     *		[int uncSize]	//仅对压缩文件(compress==1)可用，未压缩的文件体积
     *		double time
     *		[uint crc]		//版本4新增，仅对.asset文件可用，edenCRC
     * }
     *
     * DATA_SECTION:
     * (byte[])[numFiles]
     *}
     */

    /// <summary>
    /// Jab子文件解析异常
    /// </summary>
    public class JabChildFileParseException : NotImplementedException
    {
        public JabChildFileParseException(string iJabName, byte iSuffix, long iPosition) : 
            base($"Unknown JabFileSuffix!(JabName:{iJabName} Suffix:{iSuffix} StreamPosition:{iPosition})") { }
    }
    
    
    /// <summary>
    /// 工具类 : JAB（Joint Asset Bundle）
    /// </summary>
    public static class UtilsJab
    {

#region Decode

        /// <summary>
        /// Jab文件解码
        /// <para>* 线程安全</para>
        /// </summary>
        /// <param name="iJabFilePath">Jab文件</param>
        /// <param name="iDecodeDir">解码释放目标目录</param>
        /// <param name="iBufferSize">读写缓存空间大小(单位:Byte)</param>
        /// <returns>true:成功; false:失败;</returns>
        public static bool Decode(string iJabFilePath, string iDecodeDir, int iBufferSize = 2048)
        {
            var succeeded = true;
            try
            {
                succeeded = Decode(iJabFilePath, iDecodeDir, Encoding.UTF8, iBufferSize);
            }
            catch (Exception exp)
            {
                HLogger.Fatal($"UtilsJab::Decode():Failed!(File:{iJabFilePath}\n  Exception:{exp.GetType().Name}\n  Message:{exp.Message}\n  StackTrace:{exp.StackTrace}");
                succeeded = false;
            }

            return succeeded;
        }

        /// <summary>
        /// Jab文件解码
        /// <para>* 线程安全</para>
        /// </summary>
        /// <param name="iJabFilePath">Jab文件</param>
        /// <param name="iDecodeDir">解码释放目标目录</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iBufferSize">读写缓存空间大小(单位:Byte)</param>
        /// <returns>true:成功; false:失败;</returns>
        public static bool Decode(string iJabFilePath, string iDecodeDir, Encoding iEncoding, int iBufferSize = 2048)
        {
            HLogger.Info($"UtilsJab::Decode():解压文件:{iJabFilePath}");
            var succeeded = true;
            if (File.Exists(iJabFilePath))
            {
                var jabFileName = UtilsIo.GetFileName(iJabFilePath);
                using (var fs = new FileStream(iJabFilePath, FileMode.Open, FileAccess.Read))
                {
                    succeeded = Decode(fs, jabFileName, iDecodeDir, iEncoding, iBufferSize);
                }
            }
            else
            {
                HLogger.Error($"UtilsJab::Decode():解压Jab文件不存在!(File:{iJabFilePath})");
                succeeded = false;
            }

            return succeeded;
        }

        /// <summary>
        /// Jab文件解码
        /// <para>* 线程安全</para>
        /// </summary>
        /// <param name="iStream">文件流</param>
        /// <param name="iJabName">Jab文件名</param>
        /// <param name="iDecodeDir">解码释放目标目录</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iBufferSize">读写缓存空间大小(单位:Byte)</param>
        /// <returns>true:成功; false:失败;</returns>
        public static bool Decode(FileStream iStream, string iJabName, string iDecodeDir, Encoding iEncoding, int iBufferSize = 2048)
        {
            if (null == iStream || string.IsNullOrEmpty(iJabName) || string.IsNullOrEmpty(iDecodeDir)) return false;

            var costTimer = new Stopwatch();
            var succeeded = true;
            costTimer.Start();
            
            if (!Directory.Exists(iDecodeDir))
            {
                if (!UtilsIo.CheckAndCreateDirByFullDir(iDecodeDir))
                {
                    
                    HLogger.Error($"UtilsJab::Decode():UtilsIo.CheckAndCreateDirByFullDir() Failed!(JabName:{iJabName} Dir:{iDecodeDir})");
                    costTimer.Stop();
                    return false;	
                }
            }
            
            iStream.Position = 0;
            
            // 开始读写
            using (var reader = new BinaryReader(iStream))
            {
                var header = LoadHeader(reader);
                if (null == header || !header.Valid)
                {
                    HLogger.Error($"UtilsJab::Decode():LoadHeader() Failed!(JabName:{iJabName} Dir:{iDecodeDir})");
                    succeeded = false;
                }

                // 成功
                if (succeeded)
                {
                    var jabFileSize = UtilsMath.GetDataDisplaySize(iStream.Length, false);
                    if (header.Compress)
                    {
                        HLogger.Info($"JAB({iJabName})::即将解压缩, 包含:{header.ChildCount}个文件，文件大小:{jabFileSize} 到目录:{iDecodeDir}");
                    }
                    else
                    {
                        HLogger.Info($"JAB({iJabName})::即将解压缩, 包含:{header.ChildCount}个文件，文件大小:{jabFileSize} 到目录:{iDecodeDir}");
                    }

                    var childCount = header.ChildCount;
                    var lastChildRelativePath = string.Empty;
                    for (var idx = 0; idx < childCount; ++idx)
                    {
                        var jabChildFile = LoadJabChildFile(reader, header, iJabName, iEncoding, ref lastChildRelativePath);
                        if (null == jabChildFile || !jabChildFile.Valid)
                        {
                            HLogger.Error($"UtilsJab::Decode():LoadJabChildFile() Failed!(JabName:{iJabName} Dir:{iDecodeDir} Child:{idx}/{childCount})");
                            succeeded = false;
                            break;
                        }
                        header.AddChild(jabChildFile);
                        
                        var path = jabChildFile.Path;
                        var targetPath = iDecodeDir.EndsWith("/") ? $"{iDecodeDir}{path}" : $"{iDecodeDir}/{path}";
                        if (!File.Exists(targetPath))
                        {
                            var targetDir = UtilsIo.GetFileDirByPath(targetPath);
                            if (!UtilsIo.CheckAndCreateDirByFullDir(targetDir))
                            {
                                HLogger.Error($"ExpendStream():UtilsIo.CheckAndCreateDirByFullDir() Failed!(Dir:{targetDir})");
                                succeeded = false;
                                break;
                            }
                        }
                        
                        // 文件解压/解密
                        var jabChildFileSize = 0;
                        var writer = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.Read);
                        var savePosition = iStream.Position;
                        iStream.Position = header.DataSectionGOffset + jabChildFile.DataLocalOffset;
                        // 数据段有zlib压缩
                        jabChildFileSize = header.Compress ? jabChildFile.UncSize : jabChildFile.Size;
                        var buffer = new byte[iBufferSize];
                        var restFileBytes = jabChildFileSize;
                        if (header.Compress)
                        {
                            using (var inputStream = new InflaterInputStream(iStream))
                            {
                                do
                                {
                                    var dstReadBytes = iBufferSize <= restFileBytes ? iBufferSize : restFileBytes;
                                    // 重新定位
                                    var readLenTmp = inputStream.Read(buffer, 0, dstReadBytes);
                                    inputStream.IsStreamOwner = false;

                                    // 写入文件
                                    if (!UtilsIo.WriteToFile(writer, buffer, readLenTmp))
                                    {
                                        HLogger.Error(
                                            $"ExpendStream():UtilsIo.WriteToFile() Failed!!(Jab:{iJabName} Child:{targetPath} Size:{jabChildFileSize})");
                                        succeeded = false;
                                    }
                                    // 计算自己文件剩余字节数
                                    restFileBytes -= readLenTmp;
                                }
                                while (0 < restFileBytes);
                            }
                            buffer = null;
                        }
                        // 非压缩格式
                        else
                        {
                            do
                            {
                                var dstReadBytes = iBufferSize <= restFileBytes ? iBufferSize : restFileBytes;
                                var readLenTmp = iStream.Read(buffer, 0, dstReadBytes);
                                // 写入文件
                                if (!UtilsIo.WriteToFile(writer, buffer, readLenTmp))
                                {
                                    HLogger.Error($"ExpendStream():UtilsIo.WriteToFile() Failed!!(Jab:{iJabName} Child:{targetPath} Size:{jabChildFileSize})");
                                    succeeded = false;
                                }
                                
                                // 计算自己文件剩余字节数
                                restFileBytes -= readLenTmp;
                            } while (0 < restFileBytes);
                            buffer = null;
                        }
                        
                        writer.Flush();
                        writer.Close();
                        writer.Dispose();
                        writer = null;
                        
                        // 成功
                        if (succeeded)
                        {
                            iStream.Position = savePosition;
                            var jabChildFileSizeTxt = UtilsMath.GetDataDisplaySize(jabChildFileSize);
                        
                            HLogger.Info(header.Compress
                                ? $"JAB({iJabName})::解压缩, Path:{path}({jabChildFileSizeTxt})"
                                : $"JAB({iJabName})::解包, Path:{path}({jabChildFileSizeTxt})");
                        }
                        
                        // 中断处理
                        if(!succeeded) break;
                        
                        // 解压出来的子文件校验
                        if (jabChildFile.CrcValid)
                        {
                            // 子文件大小校验
                            var childFileCrcCode = UtilsIo.GenerateCRCByFile(targetPath, out var oChildFileSize);
                            if (oChildFileSize != jabChildFileSize)
                            {
                                HLogger.Error($"ExpendStream():Child file size is not match!!(Jab:{iJabName} Child:{targetPath} Size:{jabChildFileSize}/{oChildFileSize})");
                                succeeded = false;
                            }
                                
                            // 子文件Crc校验
                            if (childFileCrcCode != jabChildFile.Crc)
                            {
                                HLogger.Error($"ExpendStream():Child file crc is not match!!(Jab:{iJabName} Child:{targetPath} Crc:{jabChildFile.Crc}/{childFileCrcCode})");
                                succeeded = false;
                            }
                                
                        }
                        else
                        {
                            // 子文件大小校验
                            var childFileSize = UtilsIo.GetFileSize(targetPath);
                            if (childFileSize != jabChildFileSize)
                            {
                                HLogger.Error($"ExpendStream():Child file size is not match!!(Jab:{iJabName} Child:{targetPath} Size:{jabChildFileSize}/{childFileSize})");
                                succeeded = false;
                            }
                        }

                        // 中断处理
                        if(!succeeded) break;
                    }
                }
                
            }
            
            costTimer.Stop();
            var costTimeTxt = UtilsTime.ConvertToDisplayTime(costTimer.ElapsedMilliseconds, false, 2);
            if (succeeded)
            {
                HLogger.Info($"JAB({iJabName})::解包成功!(+ {costTimeTxt})");
            }
            else
            {
                HLogger.Error($"JAB({iJabName})::解包失败!(+ {costTimeTxt})");
            }
            
            return succeeded;
        }

#endregion
        
#region Header

        /// <summary>
        /// 加载Jab文件头部
        /// </summary>
        /// <param name="iReader">二进制文件Reader</param>
        /// <returns>Jab文件头部</returns>
        public static JabHeader LoadHeader(BinaryReader iReader)
        {
            return LoadHeader(iReader, Encoding.UTF8);
        }
        
        /// <summary>
        /// 加载Jab文件头部
        /// </summary>
        /// <param name="iReader">二进制文件Reader</param>
        /// <param name="iEncoding">字节编码</param>
        /// <returns>Jab文件头部</returns>
        public static JabHeader LoadHeader(BinaryReader iReader, Encoding iEncoding)
        {
            if (null == iReader) return null;
            var version = iReader.ReadByte();
            
            //版本2，JAB是否压缩
            var compress = 0;
            if (2 <= version)
            {
                compress = iReader.ReadByte();
            }
            var dataSectionGOffset = iReader.ReadUInt32();
            var childCount = iReader.ReadUInt32();
            var header = JabHeader.Create(version, 1 <= compress, dataSectionGOffset, childCount);
            if (null == header || !header.Valid) return null;

            if (3 <= version)
            {
                // 解压到文件夹的前缀
                var prefixLen = iReader.ReadUInt16();
                if (0 < prefixLen)
                {
                    var prefixBytes = iReader.ReadBytes(prefixLen);
                    var prefixStr = iEncoding.GetString(prefixBytes);
                    if (!prefixStr.EndsWith("/"))
                    {
                        prefixStr = $"{prefixStr}/";
                    }
                    header.Prefix = prefixStr;
                }
            }
            
            return header;
        }
        
#endregion

#region Children

        /// <summary>
        /// 加载Jab中子文件的信息
        /// </summary>
        /// <param name="iReader">二进制文件Reader</param>
        /// <param name="iHeader">Jab头部信息</param>
        /// <param name="iJabName">Jab文件名</param>
        /// <param name="oLastChildRelativePath">上一个子文件相对路径</param>
        /// <returns>Jab中子文件的信息</returns>
        /// <exception cref="JabChildFileParseException">Jab子文件解析异常</exception>
        private static JabChildFile LoadJabChildFile(BinaryReader iReader, JabHeader iHeader, string iJabName, ref string oLastChildRelativePath)
        {
            return LoadJabChildFile(iReader, iHeader, iJabName, Encoding.UTF8, ref oLastChildRelativePath);
        }

        /// <summary>
        /// 加载Jab中子文件的信息
        /// </summary>
        /// <param name="iReader">二进制文件Reader</param>
        /// <param name="iHeader">Jab头部信息</param>
        /// <param name="iJabName">Jab文件名</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="oLastChildRelativePath">上一个子文件相对路径</param>
        /// <returns>Jab中子文件的信息</returns>
        /// <exception cref="JabChildFileParseException">Jab子文件解析异常</exception>
        private static JabChildFile LoadJabChildFile(BinaryReader iReader, 
            JabHeader iHeader, string iJabName, Encoding iEncoding, ref string oLastChildRelativePath)
        {
            if (null == iReader || null == iHeader || !iHeader.Valid || string.IsNullOrEmpty(iJabName)) return null;

            var jabFile = JabChildFile.Create(iJabName);
            if (null == jabFile) return null;
            
            // 路径便宜为止
            var pathOffset = iReader.ReadByte();
            // 文件后缀类型
            var suffix = iReader.ReadByte();
            // 路径长度
            var pathLength = iReader.ReadUInt16();
            var pathTmp = string.Empty;
            if (0 < pathLength)
            {
                var pathBytes = iReader.ReadBytes(pathLength);
                pathTmp = iEncoding.GetString(pathBytes);
            }
            
            // 计算路径偏移
            var lastChildPath = oLastChildRelativePath;
            if (!string.IsNullOrEmpty(lastChildPath) && pathOffset <= lastChildPath.Length)
            {
                // TODO:相对路径单个目录长度不能超过8？
                pathTmp = lastChildPath.Substring(0, pathOffset) + pathTmp;
            }

            var jabFileSuffix = (JabFileSuffix)suffix;
            switch (jabFileSuffix)
            {
                case JabFileSuffix.None:
                    break;
                case JabFileSuffix.Asset:
                    pathTmp = $"{pathTmp}.asset";
                    break;
                case JabFileSuffix.AssetManifest:
                    pathTmp = $"{pathTmp}.asset.manifest";
                    break;
                case JabFileSuffix.PrefabAsset:
                    pathTmp = $"{pathTmp}.prefab.asset";
                    break;
                case JabFileSuffix.PrefabAssetManifest:
                    pathTmp = $"{pathTmp}.prefab.asset.manifest";
                    break;
                case JabFileSuffix.Manifest:
                    pathTmp = $"{pathTmp}.manifest";
                    break;
                default:
                    throw new JabChildFileParseException(iJabName, suffix, iReader.BaseStream.Position);
                    break;
            }
            jabFile.DataLocalOffset = iReader.ReadUInt32();
            jabFile.Size = iReader.ReadInt32();
            jabFile.UncSize = 0;
            if (iHeader.Compress)
            {
                jabFile.UncSize = iReader.ReadInt32();
            }
            jabFile.Time = iReader.ReadDouble();
            
            if (4 <= iHeader.Version)
            {
                // 仅仅*.asset的资源包才有Crc码
                if (pathTmp.EndsWith(".asset"))
                {
                    jabFile.Crc = iReader.ReadUInt32();
                }
            }

            oLastChildRelativePath = pathTmp;
            jabFile.Path = iHeader.Prefix + pathTmp;
            return jabFile;
        }

#endregion

#region Desc

        /// <summary>
        /// 解压/解密Desc文件
        /// </summary>
        /// <param name="iBytes">字节数组</param>
        /// <returns>解压/解密后的字节数组</returns>
        public static byte[] DecompressDesc(byte[] iBytes)
        {
            if (null == iBytes || 0 >= iBytes.Length) return null;

            var bytesTmp = iBytes;
            // 解密和解压
            bytesTmp[0] = (byte)~bytesTmp[0];

            using (var stream = new MemoryStream(bytesTmp))
            {
                var m1 = new MemoryStream();
                using (var inputStream = new InflaterInputStream(stream))
                {
                    inputStream.CopyTo(m1);
                }
                bytesTmp = m1.ToArray();
            }
            return bytesTmp;
        }

        public static void DecodeDescBin(string iName, byte[] iBytes)
        {
            var bytesTmp = iBytes;
            if (0 != bytesTmp[0])
            {
                bytesTmp = DecompressDesc(bytesTmp);
            }
        }

#endregion

    }
}
